import { reactive } from 'vue'
import { Buffer } from 'buffer'
import { useCookies } from 'vue3-cookies'
import BigNumber from 'bignumber.js'

// console.log('xx=', Buffer);
window.Buffer = Buffer

// import WalletConnectProvider from "@walletconnect/web3-provider";
import WalletConnect from '@walletconnect/client'
import QRCodeModal from '@walletconnect/qrcode-modal'

let t
function setDappLocate(tt) {
    t = tt
}

import {
    ERC20_ABI,
    MARKET_ABI,
    MARKET_ABI2,
    MARKET_ADDRESS,
    MARKET_ADDRESS2,
    NFT_ABI,
    NFT_ADDRESS,
} from './abi'

var abi = ERC20_ABI

const chainId = '0x38' // bsc mainnet
// const chainId = "0x61"  // nsc testnet

var web3 = {}
var scanUrl = ''

if (__DAPP__) {
    var web3Url =
        chainId === '0x38'
            ? 'https://bsc-dataseed1.ninicoin.io'
            : 'https://data-seed-prebsc-1-s1.binance.org:8545/'
    scanUrl =
        chainId === '0x38'
            ? 'https://www.bscscan.com/'
            : 'https://testnet.bscscan.com/'
    console.log('use web3', chainId, web3Url)
    web3 = new Web3(
        // "http://127.0.0.1:8545" //
        web3Url,
    )
}

var address = ''

const state = reactive({
    active: false,
    loaded: undefined,
    address: '',
    connected: false,
    connectType: '', // metamask|connect|http
    token: '',
})

const { cookies } = useCookies()

if (!__DAPP__) {
    var _token = cookies.get('token3')
    if (_token && _token.length === 32) {
        state.token = _token
        state.connectType = 'http'
        state.connected = true
    }
}

const listeners = []

function addConnectListener(listner) {
    listeners.push(listner)
}

const addNetwork = (params) =>
    window.ethereum
        .request({ method: 'wallet_addEthereumChain', params })
        .then(() => {
            // setLog([...log, `Switched to ${params[0].chainName} (${parseInt(params[0].chainId)})`])
            // setChainId(parseInt(params[0].chainId))
            console.log('set ok ?')
        })
        .catch((error) => console.error(`Error: ${error.message}`))

const bridge = 'https://bridge.walletconnect.org'
// const bridge = "http://192.168.2.188:7000";
// create new connector
let connector = {
    on: () => {},
    killSession: () => {},
}

if (__DAPP__) {
    connector = new WalletConnect({
        bridge,
        qrcodeModal: QRCodeModal,
        qrcodeModalOptions: {
            mobileLinks: [
                'argent',
                'imtoken',
                'trust',
                'metamask',
                'tokenpocket',
                'bitpie',
                'math',
                'safepal',
                'pillar',
                // 'equalwallet',
                // 'coolwallet',
                // 'xwallet',
                // 'atomic',
                // 'cosmostation',
                // 'infinity',
                'coin98',
                'bitkeep',
                'bitpay',
            ],
        },
    })

    connector.on('connect', (error, payload) => {
        if (error) {
            throw error
        }

        // Get provided accounts and chainId
        const { accounts, chainId } = payload.params[0]

        // alert(chainId + '-' + JSON.stringify(accounts))
        state.address = accounts[0]
        state.connected = true
        state.connectType = 'connect'
        listeners.map((fn) => fn())

        // console.log('connect==', accounts, chainId);
    })

    connector.on('disconnect', (error, payload) => {
        console.log('disconnect')
        state.address = ''
        state.connected = false
    })
}

async function disconnect() {
    console.log('disconnect..')
    state.connected = false
    state.address = ''
    state.token = ''
    cookies.remove('token3')
    connector.killSession()
}

async function connectMetamask() {
    let _chainId = await ethereum.request({ method: 'eth_chainId' })
    console.log('chainId=', _chainId)
    if (_chainId !== chainId) {
        await addNetwork([
            {
                chainId: '0x38',
                chainName: 'Binance Smart Chain',
                nativeCurrency: {
                    name: 'BNB',
                    symbol: 'BNB',
                    decimals: 18,
                },
                rpcUrls: ['https://bsc-dataseed1.ninicoin.io'],
                blockExplorerUrls: ['https://bscscan.com/'],
            },
        ])
        _chainId = await ethereum.request({ method: 'eth_chainId' })
        console.log('chainId2=', _chainId)
        if (_chainId !== chainId) {
            throw Error('Error exchange chain node')
        }
    }
    ethereum.on('networkChanged', () => {
        location.reload()
    })
    ethereum.on('accountsChanged', () => {
        location.reload()
    })
    console.log('eth_requestAccounts==')
    const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
    // address = ethereum.selectedAddress
    console.log('aa=', accounts, ethereum.selectedAddress)
    if (accounts) {
        state.address = accounts[0]
        state.connected = true
        state.connectType = 'metamask'
        listeners.map((fn) => fn())
    }
    return address
}

function isMetamask() {
    return (
        !window.Bitpie &&
        typeof window.ethereum !== 'undefined' &&
        window.ethereum.isMetaMask
    )
    // return __DEBUG__ && typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask
}

async function connect(create) {
    // 24b861d722036a724d8918051be848ab
    if (!__DAPP__) {
        return
    }

    if (isMetamask()) {
        await connectMetamask()
        return
    }

    window.connector = connector
    if (!connector.connected) {
        // console.log('connection not found=', create)
        // console.log(connector);
        if (create) {
            // if (connector.session) {
            //     console.log('kill=');
            //     //await connector.killSession()
            // }
            // console.log('create');
            await connector.createSession({ chainId: chainId * 1 })
        }
    } else {
        const { connected, accounts, peerMeta } = connector
        console.log('connected cache=', connected, accounts, peerMeta)
        // connector.killSession()
        if (accounts) {
            state.address = accounts[0]
            state.connected = true
            state.connectType = 'connect'
            listeners.map((fn) => fn())
        } else {
            // connector.killSession()
            // connector.connected = false
        }
    }
}

async function getAddress() {
    // return "0x9EFBcc05704BC1F3BE3cD40EC066623Bb886Cc46"  // test address
    return state.address
}

async function sign(url, cancelReact) {
    if (state.connectType === 'http') {
        return '&token=' + state.token
    }

    var msg = 'time' + (Math.floor(Date.now() / 1000) + 60 * 30)
    var address = state.address

    if (state.connectType === 'metamask') {
        let method = 'eth_sign'
        let data
        if (window.bitkeep) {
            data = msg
        } else if (window.Bitpie) {
            method = 'personal_sign'
            // data = msg
            // data = 'login'
            data = web3.utils.keccak256(
                '\x19Ethereum Signed Message:\n' + msg.length + msg,
            )
        } else {
            data = web3.utils.keccak256(
                '\x19Ethereum Signed Message:\n' + msg.length + msg,
            )
        }
        var s
        try {
            var _msg = localStorage.getItem('msg')
            var _sign = localStorage.getItem('sign')
            if (_msg && _sign) {
                var t = _msg.replace('time', '') * 1
                if (t > Date.now() / 1000) {
                    console.log('sign not expire')
                    msg = _msg
                    s = _sign
                }
            }
            if (!s) {
                console.log('request=', method, address, data)
                s = await ethereum.request({
                    method: method,
                    params: [address, data],
                })
                localStorage.setItem('msg', msg)
                localStorage.setItem('sign', s)
            }
            // s = await ethereum.request({ method: 'personal_sign', params: [ethereum.selectedAddress, 'login'] }).then(function(res){console.log('sign=',res)});
        } catch (e) {
            console.log('cancelReact', cancelReact)
            if (cancelReact) {
                state.loaded = false
            }
            throw Error('sign error')
        }
        let params =
            (url.indexOf('?') === -1 ? '?' : '&') +
            `address=${state.address}&msg=${msg}&sign=${s}`
        console.log(params)
        return params
    } else if (state.connectType === 'connect') {
        console.log('request sign msg', address, msg)
        const s = await connector.signMessage([address, msg])
        console.log('sign result==', s)
        return `&address=${state.address}&msg=${msg}&sign=${s}`
    } else if (state.connectType === 'http') {
        return ''
    } else {
        throw Error('no connect wallet')
    }
}

async function transfer(contractAddress, toAddr, amount, decimal) {
    var selfAddress = await getAddress()
    var ethBalance = await web3.eth.getBalance(selfAddress)
    ethBalance = (ethBalance / 10 ** decimal).toFixed(8) * 1
    console.log('eth balance=', ethBalance)
    if (ethBalance < 0.0005) {
        Toast(t('tips.insufficient'))
        throw Error('Insufficient Amount')
    }

    var wei = '0x' + (amount * 10 ** decimal).toString(16)
    const balance = await balanceOf(contractAddress, selfAddress, decimal)
    // console.log('balance=', balance);
    if (amount > balance) {
        Toast(t('tips.insufficient'))
        throw Error('Insufficient Amount')
    }

    const contract = new web3.eth.Contract(abi, contractAddress)
    var fn = contract.methods.transfer(toAddr, wei)
    return request(fn, contractAddress)
}

async function balanceOf(contractAddress, address, decimal) {
    const contract = new web3.eth.Contract(abi, contractAddress)
    var balance = await contract.methods.balanceOf(address).call()
    console.log('balance=', balance)
    balance = (balance / 10 ** decimal).toFixed(2)
    return balance
}

async function listNft(contractAddress, address) {
    const contract = new web3.eth.Contract(NFT_ABI, contractAddress)
    var balance = await contract.methods.balanceOf(address).call()
    // console.log('balance=', balance);
    var tokens = []
    if (balance > 0) {
        for (let i = 0; i < balance; i++) {
            var tokenId = await contract.methods
                .tokenOfOwnerByIndex(address, i)
                .call()
            tokens.push(tokenId)
        }
    }
    return tokens
}

async function isApprovedForAll(address, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const contract = new web3.eth.Contract(NFT_ABI, NFT_ADDRESS)
    //1、调用代币的 allowance 方法
    var ret = await contract.methods
        .isApprovedForAll(address, _marketAddress)
        .call()
    return ret
}

async function allowanceNFT(address, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const contract = new web3.eth.Contract(NFT_ABI, NFT_ADDRESS)
    //1、调用代币的 allowance 方法
    var ret = await contract.methods
        .isApprovedForAll(address, _marketAddress)
        .call()
    console.log(address, _marketAddress, NFT_ADDRESS, ret)
    if (!ret) {
        //1、调用代币的 allowance 方法
        console.log('setApprovalForAll=', _marketAddress, address)
        var result = await request(
            contract.methods.setApprovalForAll(_marketAddress, true),
            NFT_ADDRESS,
        )
        console.log('allowance result：', result)
    }
    return ret
}

async function sell(tokenId, tokenAddress, _price, _currency, _iserc, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const _marketABI = type === 1 ? MARKET_ABI2 : MARKET_ABI
    const contract = new web3.eth.Contract(_marketABI, _marketAddress)
    const price = web3.utils.toWei(_price)
    console.log('sell=', tokenId, price, tokenAddress, _currency, _iserc, type)
    var fn
    if (type === 1) {
        fn = contract.methods.sell(
            tokenId,
            price,
            tokenAddress,
            _currency,
            _iserc,
            type,
        )
    } else {
        fn = contract.methods.sell(
            tokenId,
            price,
            tokenAddress,
            _currency,
            _iserc,
        )
    }
    return request(fn, _marketAddress)
}

async function buy(id, _currency, _iserc, price, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const _marketABI = type === 1 ? MARKET_ABI2 : MARKET_ABI
    const contract = new web3.eth.Contract(_marketABI, _marketAddress)
    var value = '0x' + (price * 1).toString(16)
    console.log('buy=', id, _currency, _iserc, price, value)
    var fn = contract.methods.buy(id, _currency, _iserc)
    return request(fn, _marketAddress, value)
}

async function cancelSell(id, _iserc, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const _marketABI = type === 1 ? MARKET_ABI2 : MARKET_ABI
    const contract = new web3.eth.Contract(_marketABI, _marketAddress)
    console.log('buy=', id, NFT_ADDRESS, _iserc)
    var fn = contract.methods.cancelSell(id, _iserc)
    return request(fn, _marketAddress)
}

async function allowance(contractAddress, fromAddr, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const contract = new web3.eth.Contract(ERC20_ABI, contractAddress)
    var result = await contract.methods
        .allowance(fromAddr, _marketAddress)
        .call()
    return result
}

async function getBalance(contractAddress, address) {
    const contract = new web3.eth.Contract(ERC20_ABI, contractAddress)
    var balance = await contract.methods.balanceOf(address).call()
    // var decimal = await contract.methods.decimals().call()
    // console.log('getRelations=',decimal);
    // return balance/(10**decimal)
    return balance
}

async function approve(contractAddress, value, type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const contract = new web3.eth.Contract(ERC20_ABI, contractAddress)
    var fn = contract.methods.approve(_marketAddress, value)
    return request(fn, contractAddress)
}

async function transferFrom721(address, to, tokenId) {
    const contract = new web3.eth.Contract(NFT_ABI, NFT_ADDRESS)
    var fn = contract.methods.safeTransferFrom(address, to, tokenId)
    return request(fn, NFT_ADDRESS)
}

async function feeRate(type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const _marketABI = type === 1 ? MARKET_ABI2 : MARKET_ABI
    const contract = new web3.eth.Contract(_marketABI, _marketAddress)
    if (type === 1) {
        return contract.methods.feeRate(type).call()
    }
    return contract.methods.feeRate().call()
}
async function burnRate(type) {
    const _marketAddress = type === 1 ? MARKET_ADDRESS2 : MARKET_ADDRESS
    const _marketABI = type === 1 ? MARKET_ABI2 : MARKET_ABI
    const contract = new web3.eth.Contract(_marketABI, _marketAddress)
    if (type === 1) {
        return contract.methods.burnRate(type).call()
    }
    return contract.methods.burnRate().call()
}

async function typeRate(type) {
    const contract = new web3.eth.Contract(MARKET_ABI2, MARKET_ADDRESS2)
    let typeRate = await contract.methods.typeRate(type).call()
    console.log('typeRate=', typeRate)
    return typeRate
}

async function request(fn, contractAddress, value = 0) {
    var data = fn.encodeABI()
    var fromAddr = await getAddress()
    console.log('estimateGas from', fromAddr, value)
    var gas = await fn.estimateGas({
        from: fromAddr,
        value: value,
    })
    console.log('gas=', gas)

    const transactionParameters = {
        // nonce: '0x00', // ignored by MetaMask
        // gasPrice: '0x09184e72a000', // customizable by user during MetaMask confirmation.
        // gas: '0x2710', // customizable by user during MetaMask confirmation.
        gas: `${gas}`,
        to: contractAddress, // Required except during contract publications.
        from: fromAddr, // must match user's active address.
        value: value, // Only required to send ether to the recipient from the initiating external account.
        data: data, // Optional, but used for defining smart contract creation and interaction.
        // chainId: '128', // Used to prevent transaction reuse across blockchains. Auto-filled by MetaMask.
        chainId: chainId * 1,
    }

    console.log('rawTx=', transactionParameters)

    var txHash

    if (state.connectType === 'metamask') {
        txHash = await ethereum.request({
            method: 'eth_sendTransaction',
            params: [transactionParameters],
        })
        return txHash
    } else if (state.connectType === 'connect') {
        txHash = await connector.sendTransaction({
            gas: `${gas}`,
            to: contractAddress, // Required except during contract publications.
            from: fromAddr, // must match user's active address.
            value: '0x00', // Only required to send ether to the recipient from the initiating external account.
            data: data,
            chainId: chainId * 1,
        })
    }

    console.log('submit txHash=', txHash)

    return txHash
}

async function watch(txid) {
    return new Promise((resolve) => {
        var timer = setInterval(async () => {
            var tx = await web3.eth.getTransaction(txid)
            console.log('tx===', tx)
            if (tx && tx.blockNumber > 0) {
                clearInterval(timer)
                resolve(true)
            }
        }, 2000)
    })
}

function mul(a, b) {
    // console.log('mul=', a, b)
    // console.log(web3)
    var c = new BigNumber(a)
    var d = new BigNumber(b)
    let v = c.multipliedBy(d);
    // console.log('mul=', c, d, v)
    return v
}

function div(a, b) {

}

function comp(a, b) {
    console.log('comp=', a, b)
    var c = !(a instanceof BigNumber) ? new BigNumber(a) : a
    var d = !(b instanceof BigNumber) ? new BigNumber(b) : b
    return c.comparedTo(d)
}

export {
    state,
    getAddress,
    transfer,
    sign,
    connect,
    disconnect,
    addConnectListener,
    setDappLocate,
    listNft,
    allowanceNFT,
    sell,
    buy,
    cancelSell,
    allowance,
    approve,
    getBalance,
    transferFrom721,
    watch,
    scanUrl,
    feeRate,
    burnRate,
    typeRate,
    isApprovedForAll,
    mul,
    div,
    comp,
}
